package db

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
)

const (
	typeFlow = iota
	typeString
	typeInt
	typeFloat
)

type Flow interface {
	Encode() ([]byte, error)
}

type Entry struct {
	lenKey   uint64
	lenValue uint64

	vType uint8 // value 的 type 的类型

	Key   string
	Value []byte
}

func NewEntry(key string, value interface{}) (*Entry, error) {
	if key == "" {
		return nil, errors.New("key can't be empty")
	}

	var vType uint8

	bufValue := bytes.NewBuffer([]byte{})

	// 断言类型存储
	switch v := value.(type) {

	case int:
		vType = typeInt
		err := binary.Write(bufValue, binary.BigEndian, int64(v))
		if err != nil {
			return nil, err
		}
	case float64, float32:
		vType = typeFloat
		err := binary.Write(bufValue, binary.BigEndian, value)
		if err != nil {
			return nil, err
		}
	case Flow:
		buf, err := v.Encode()
		if err != nil {
			return nil, err
		}
		vType = typeFlow
		bufValue.Write(buf)
	case string:
		bufValue.WriteString(v)
		vType = typeString
	default:
		return nil, errors.New("the type that could not be resolved")
	}

	return &Entry{
		lenKey:   uint64(len(key)),
		lenValue: uint64(bufValue.Len()),
		vType:    vType,
		Key:      key,
		Value:    bufValue.Bytes(),
	}, nil
}

func (e *Entry) Dump() ([]byte, error) {
	buf := bytes.NewBuffer([]byte{})

	// lenKey
	b := make([]byte, 8)
	binary.BigEndian.PutUint64(b, e.lenKey)
	buf.Write(b)

	// lenValue
	binary.BigEndian.PutUint64(b, e.lenValue)
	buf.Write(b)

	// vType
	buf.WriteByte(e.vType)

	// key
	buf.WriteString(e.Key)

	// value
	buf.Write(e.Value)

	return buf.Bytes(), nil
}

func (e *Entry) Load(r io.Reader) error {
	head := make([]byte, 17)
	n, _ := r.Read(head)
	if n != 17 {
		return errors.New("parse head err")
	}

	// load info
	e.lenKey = binary.BigEndian.Uint64(head[:8])
	e.lenValue = binary.BigEndian.Uint64(head[8:16])
	e.vType = head[16]

	length := e.lenKey + e.lenValue
	body := make([]byte, length)

	n, _ = r.Read(body)
	if n != int(length) {
		return errors.New("parse body err")
	}

	e.Key = string(body[:e.lenKey])
	e.Value = body[e.lenKey:]

	return nil
}

func (e *Entry) Parse() (interface{}, error) {
	switch e.vType {
	case typeFlow:
		return e.Value, nil
	case typeString:
		return string(e.Value), nil
	case typeInt:
		buf := bytes.NewBuffer(e.Value)
		var target int64
		err := binary.Read(buf, binary.BigEndian, &target)
		if err != nil {
			return nil, err
		}

		return int(target), nil
	case typeFloat:
		buf := bytes.NewBuffer(e.Value)
		var target float64
		err := binary.Read(buf, binary.BigEndian, &target)
		if err != nil {
			return nil, err
		}

		return target, nil
	}

	return nil, errors.New("parse err")
}
